﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Developex.DatabaseWrapper.Fields
{
	/// <summary>
	/// External foreign key field
	/// </summary>
	/// <typeparam name="T">PK of foreign object field type, should be nullable if relationship is not mandatory</typeparam>
	/// <typeparam name="FRow">Foreign object type</typeparam>
	/// <remarks>
	/// On SELECT query value of foreign object PK will be set
	/// TODO: consider if PK of foreign object consists of several fields
	/// </remarks>
	public class ExternalForeignKeyField<T, FRow> : ForeignKeyFieldBase<T, FRow> where FRow : Row, new()
	{
		#region Private fields

		/// <summary>
		/// 
		/// </summary>
		protected Enum foreignField;

		#endregion

		#region Constructors

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		/// <param name="doFullLoad"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, T insertDefaultValue, FieldFlags flags, bool doFullLoad)
			: base(name, insertDefaultValue, flags, doFullLoad)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="doFullLoad"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, FieldFlags flags, bool doFullLoad)
			: base(name, flags, doFullLoad)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="doFullLoad"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, bool doFullLoad)
			: base(name, doFullLoad)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, T insertDefaultValue, FieldFlags flags, bool doFullLoad, bool simpleNamingMode)
			: base(name, insertDefaultValue, flags, doFullLoad, simpleNamingMode)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, FieldFlags flags, bool doFullLoad, bool simpleNamingMode)
			: base(name, flags, doFullLoad, simpleNamingMode)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, bool doFullLoad, bool simpleNamingMode)
			: base(name, doFullLoad, simpleNamingMode)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		public ExternalForeignKeyField(Enum foreignField, string name)
			: base(name)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, FieldFlags flags)
			: base(name, flags)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, FieldFlags flags, T insertDefaultValue)
			: base(name, flags, insertDefaultValue)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="cretionParameters"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, T insertDefaultValue, FieldFlags flags, bool doFullLoad, object[] cretionParameters)
			: base(name, insertDefaultValue, flags, doFullLoad, cretionParameters)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="cretionParameters"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, FieldFlags flags, bool doFullLoad, object[] cretionParameters)
			: base(name, flags, doFullLoad, cretionParameters)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="cretionParameters"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, bool doFullLoad, object[] cretionParameters)
			: base(name, doFullLoad, cretionParameters)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		/// <param name="cretionParameters"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, T insertDefaultValue, FieldFlags flags, bool doFullLoad, bool simpleNamingMode, object[] cretionParameters)
			: base(name, insertDefaultValue, flags, doFullLoad, simpleNamingMode, cretionParameters)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		/// <param name="cretionParameters"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, FieldFlags flags, bool doFullLoad, bool simpleNamingMode, object[] cretionParameters)
			: base(name, flags, doFullLoad, simpleNamingMode, cretionParameters)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="doFullLoad"></param>
		/// <param name="simpleNamingMode"></param>
		/// <param name="cretionParameters"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, bool doFullLoad, bool simpleNamingMode, object[] cretionParameters)
			: base(name, doFullLoad, simpleNamingMode, cretionParameters)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="cretionParameters"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, object[] cretionParameters)
			: base(name, cretionParameters)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="cretionParameters"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, FieldFlags flags, object[] cretionParameters)
			: base(name, flags, cretionParameters)
		{
			this.foreignField = foreignField;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="foreignField"></param>
		/// <param name="name"></param>
		/// <param name="flags"></param>
		/// <param name="insertDefaultValue"></param>
		/// <param name="cretionParameters"></param>
		public ExternalForeignKeyField(Enum foreignField, string name, FieldFlags flags, T insertDefaultValue, object[] cretionParameters)
			: base(name, flags, insertDefaultValue, cretionParameters)
		{
			this.foreignField = foreignField;
		}

		#endregion

		#region Internal

		private IField _foreignFieldFK = null;

		/// <summary>
		/// FK field of foreign objbect
		/// </summary>
		protected IField ForeignFieldFK
		{
			get
			{
				if (_foreignFieldFK == null)
				{
					FRow frow = base.Object;
					_foreignFieldFK = frow.Fields[frow.GetFieldNameByPropertyName(foreignField.ToString())];
				}
				return _foreignFieldFK;
			}
		}

		#endregion

		#region Implementation of abstract members

		///<summary>
		/// Get join expression for this field
		///</summary>
		///<returns></returns>
		public override string GetJoinExpression()
		{
			FRow frow = Object;
			string prefix = GetJoinPrefix();
			string joinTableName = prefix + frow.TableName;
			return string.IsNullOrEmpty(prefix) ? string.Format(@"
LEFT JOIN [{0}] ON [{1}].[{2}] = [{0}].[{3}]
", frow.TableName, Parent.JoinTableName, Parent.PrimaryKeyField.Name, ForeignFieldFK.Name)
				  : string.Format(@"
LEFT JOIN [{0}] [{1}] ON [{2}].[{3}] = [{1}].[{4}]
", frow.TableName, joinTableName, Parent.JoinTableName, Parent.PrimaryKeyField.Name, ForeignFieldFK.Name);
		}

		#endregion

		#region Overrides of Field

		/// <summary>
		/// Expression used in SELECT query
		/// </summary>
		public override string GetSelectExpression()
		{
			return ForeignFieldFK.GetSelectExpression();
		}

		#endregion

		#region Overrides of ForeignKeyFieldBase

		///<summary>
		/// Forein Object field
		///</summary>
		public override FRow Object
		{
			get
			{
				FRow frow = base.Object;

				// auto set FK field of foreign object
				if (!ForeignFieldFK.IsInitialized() && Parent.PrimaryKeyField.IsInitialized())
				{
					object value = Parent.PrimaryKeyField.GetValue();
					if (value != null)
						ForeignFieldFK.InitValue(value);
				}

				return frow;
			}
		}

		/// <summary>
		/// Add joins and etc.
		/// </summary>
		/// <param name="qb">query builder</param>
		/// <remarks>don't forget to call qb.</remarks>
		public override void PrepareForSelectQuery(QueryBuilder qb)
		{
			FRow frow = Object;
			int k = FieldsToLoad.IndexOf(frow.PrimaryKeyField);
			if (k == -1)
				FieldsToLoad.Insert(0, frow.PrimaryKeyField);
			else if (k != 0)
			{
				FieldsToLoad.RemoveAt(k);
				FieldsToLoad.Insert(0, frow.PrimaryKeyField);
			}
			base.PrepareForSelectQuery(qb);
		}

		#endregion

		#region ICloneable

		///<summary>
		/// Create object 
		///</summary>
		///<returns></returns>
		/// <remarks>
		/// Here should be only object creation, all data copy should be in overrided Clone function.
		/// Used only in base version of Clone function.
		/// Should be overrided for ICloneable interface.
		/// </remarks>
		protected override Field<T> CreateObjectForClone()
		{
			return new ExternalForeignKeyField<T, FRow>(null, null);
		}

		/// <summary>
		/// Clone
		/// </summary>
		/// <returns></returns>
		public override object Clone()
		{
			ExternalForeignKeyField<T, FRow> field = (ExternalForeignKeyField<T, FRow>)base.Clone();
			field.foreignField = foreignField;
			return field;
		}

		#endregion
	}
}
